package com.justgame.project.cloud.common.util;


import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.PutObjectResult;
import com.justgame.project.cloud.common.constant.Const;
import com.justgame.project.cloud.common.constant.Regex;
import com.justgame.project.cloud.common.enums.FileTypeEnum;
import com.justgame.project.cloud.common.exception.Exc;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 文件工具类
 */
@Slf4j
@Component
public class FileUtils {
    private static final OSS staticOss;

    private static final RedisUtil redisUtil;

    static {
        staticOss = AppContextUtils.getBean(OSS.class);
        redisUtil = AppContextUtils.getBean(RedisUtil.class);
    }

    public List<String> paths = new ArrayList<>();

    public static Object readOssFile(String fileUrl) {
        boolean isExist = staticOss.doesObjectExist(AliyunOssUtil.getBucket(), AliyunOssUtil.getOssFileName(fileUrl));
        if (!isExist) throw new Exc("该文件不存在！");
        OSSObject object = staticOss.getObject(AliyunOssUtil.getBucket(), AliyunOssUtil.getOssFileName(fileUrl));
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        try {
            inputStream = object.getObjectContent();
            int temp;
            while ((temp = inputStream.read()) != -1) {
                /* 解决多行空格 */
                if (((char) temp) != '\r') {
                    sb.append((char) temp);
                }
            }
            inputStream.close();
            object.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return JSONObject.parseObject(sb.toString());
    }

    public static Object readLocalFile(String filePath) {
        File file = new File(filePath);
        if (!file.exists()) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        Reader reader = null;
        try {
            reader = new InputStreamReader(new FileInputStream(file));
            int temp;
            while ((temp = reader.read()) != -1) {
                /* 解决多行空格 */
                if (((char) temp) != '\r') {
                    sb.append((char) temp);
                }
            }
            reader.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return JSONObject.parseObject(sb.toString());
    }
    public static void deleteFile(String path) {
        RedisUtil redis = AppContextUtils.getBean(RedisUtil.class);
        if (Regex.isUrl(path)) {
            staticOss.deleteObject(AliyunOssUtil.getBucket(), AliyunOssUtil.getOssFileName(path));
            /* 从Redis中删除临时信息 */
            if (redis.hExists(Const.KEY_TEMP_FILES, path)) {
                redis.hDelete(Const.KEY_TEMP_FILES, path);
            }
        } else {
            String fullPath = (Const.LOCAL_PATH + path).replaceAll("\\\\", "/");
            log.info("\n正在删除{}", fullPath);
            File file = new File(Const.LOCAL_PATH + path);
            if (file.exists()) {
                log.info("该文件存在！正在执行删除操作！");
                log.info("大小：{}，是否是文件：{}，可执行：{}，可写：{}，可读：{}",
                        file.length(), file.isFile(), file.canExecute(), file.canWrite(), file.canRead()
                );
                boolean success = file.delete();
                log.info("文件删除{}", success ? "成功" : "失败");
            }
        }
    }
    public static void deleteFiles(List<String> paths) {
        for (String path : paths) {
            deleteFile(path);
        }
    }

    private static void safeMkdir(String path) {
        //检查目录
        File uploadDir = new File(path);
        if (!uploadDir.exists()) {
            uploadDir.mkdir();
        }
        if (!uploadDir.canWrite()) {
            uploadDir.setWritable(true);
        }
    }


    /**
     * 文件大小数字转字符串
     *
     * @param len long
     * @return String
     * @author ZhouWeiBo
     */
    public static String getFileSizeStr(long len) {
        String fileSize = "0b";
        if (len >= 0 && len < 1024) {
            fileSize = (double) len + "b";
        } else if (len >= 1024 && len < 1048576) {
            fileSize = ((double) len / 1024) + "kb";
        } else if (len >= 1048576 && len < 1073741824) {
            fileSize = ((double) len / 1048576) + "mb";
        } else if (len > 1073741824) {
            fileSize = ((double) len / 1073741824) + "gb";
        }
        return fileSize;
    }

    /**
     * 获取文件保存路径中的文件后缀(默认小写)
     *
     * @param fileUrl
     * @return
     */
    public static String getFileUrlSuffix(String fileUrl) {
        if (StringUtils.isBlank(fileUrl)) return "";
        fileUrl = fileUrl.toLowerCase();
        try {
            if (Regex.isUrl(fileUrl)) {
                String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1);
                if (fileName.length() > 0 && fileName.contains(".")) {
                    return fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
                }
            } else if (fileUrl.contains(".")) {
                return fileUrl.substring(fileUrl.lastIndexOf(".") + 1).toLowerCase();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 保存文件方法
     *
     * @param uploadFiles  文件对象
     * @param typeEnum     文件类型
     * @param MaxFileCount 最大上传数量
     * @return
     */
    public List<Map<String, Object>> saveFiles(MultipartFile[] uploadFiles, FileTypeEnum typeEnum, int MaxFileCount, long MaxFileSize) {
        /* 获取请求体和响应体 */
        HttpServletRequest request = HttpContextUtil.getHttpRequest();

        List<Map<String, Object>> files = new ArrayList<>();
        /* init */
        MaxFileCount = MaxFileCount == 0 ? 1 : MaxFileCount;
        typeEnum = typeEnum == null ? FileTypeEnum.File : typeEnum;


        if (uploadFiles.length == 0) {
            throw new Exc("未选择文件！");
        } else if (uploadFiles.length > MaxFileCount) {
            throw new Exc(String.format("上传文件数量不能大于%s", MaxFileCount));
        }

        // 文件保存目录路径
        String savePath = Const.LOCAL_PATH,
                //文件保存目录URL
                saveUrl = request.getContextPath();

        //判断客户端<form>标记的enctype属性是否是“multipart/form-data"
        if (!ServletFileUpload.isMultipartContent(request)) {
            throw new Exc("未选择文件！");
        }

        //检查目录
        safeMkdir(savePath);

        //创建年月日目录，
        String ymd = new SimpleDateFormat("yyyyMMdd").format(new Date());
        savePath += Const.UserFilesPath + ymd + "/";
        saveUrl += Const.UserFilesPath + ymd + "/";
        //判断当天目录是否存在，不存在则创建文件夹
        safeMkdir(savePath);

        int skipCount = 0;
        for (MultipartFile uploadFile : uploadFiles) {
            Map<String, Object> file = new HashMap<String, Object>();
            /* 如果文件过大，跳过保存此文件 */
            if (uploadFile.getSize() > MaxFileSize) {
                skipCount += 1;
                log.error("已跳过保存文件:{}", getFileSizeStr(uploadFile.getSize()));
                continue;
            }

            log.info("正在上传文件，占据存储空间：{}", getFileSizeStr(uploadFile.getSize()));

            // 如果当前上传的文件不为空
            if (!uploadFile.isEmpty()) {

                // 获取上传文件的名字
                String fileName = uploadFile.getOriginalFilename();
                if (StringUtils.isBlank(fileName)) throw new SecurityException("未知文件名，无法上传！");

                file.put("fileName", fileName);
                file.put("fileSize", uploadFile.getSize());

                // 截取扩展名 toLowerCase用于将大写字符转换为小写  +1表示不包括.开始截取

                String fileExt = FileUtils.getFileUrlSuffix(fileName);

                // 校验该扩展名是否在该键对应的格式中 contains:当且仅当此字符串包含指定的char值序列时，返回true
                if (!typeEnum.isContains(fileExt)) {
                    throw new Exc("上传文件扩展名是不允许的扩展名。\n只允许" + Arrays.toString(typeEnum.getSuffix()) + "格式。");
                }
                file.put("fileSuffix", fileExt);

                String newFileName = String.format("%s.%s", IdUtils.create(), fileExt);
                try {
                    FileOutputStream os = new FileOutputStream(savePath + newFileName);
                    IOUtils.copy(uploadFile.getInputStream(), os);
                    os.close();
                } catch (Exception e) {
                    throw new Exc("上传文件失败。");
                }
                file.put("filePath", saveUrl + newFileName);
                paths.add(saveUrl + newFileName);
                files.add(file);
            }
        }
        if (skipCount > 0) throw new Exc("文件过大，上传失败！");
        return files;
    }

    /**
     * 此方法使用情景为：
     * 用户上传一个文件，该文件的url会存在于redis中记录并返回给前端
     * 若用户将上一步服务器返回的文件url提交到保存接口，并保存成功后则删除redis中该条记录
     * 若在时效期内不删除该条url记录，则会被定时任务捕获并向OSS提交删除该文件命令及相关后续操作
     *
     * @param request
     */
    @Async("taskExecutor")
    public void asyncClearFileTask(ServerHttpRequest request) {
        ServletServerHttpRequest req = (ServletServerHttpRequest) request;
        HttpServletRequest httpServletRequest = req.getServletRequest();
        Map<String, Object> jsonObj = JSONObject.parseObject(HttpContextUtil.getBodyString(httpServletRequest));
        if (jsonObj != null) {
            jsonObj.forEach((key, value) -> {
                checkDownObj(value);
            });
        }
    }

    private void checkDownObj(Object value) {
        if (value instanceof String && StringUtils.isNotBlank((String) value) && Regex.isUrl((String) value)) {
            if (redisUtil.hExists(Const.KEY_TEMP_FILES, value)) {
                redisUtil.hDelete(Const.KEY_TEMP_FILES, value);
                log.info("\n已删除临时文件{}", value);
            }
        } else if (value instanceof Collection) {
            List<Object> values = new ArrayList<>((Collection) value);
            values.forEach(this::checkDownObj);
        } else if (value instanceof Map) {
            ((Map<String, Object>) value).forEach((k, v) -> {
                checkDownObj(v);
            });
        }
    }

    /**
     * 阿里云上传OSS对象
     *
     * @param uploadFiles  文件对象
     * @param typeEnum     文件类型
     * @param MaxFileCount 最大上传数量
     * @param isCompression 是否开启压缩
     */
    @SneakyThrows
    public List<Map<String, Object>> AliYunOssUpload(MultipartFile[] uploadFiles, FileTypeEnum typeEnum, int MaxFileCount, long MaxFileSize,boolean isCompression) {
        List<Map<String, Object>> filesMap = new ArrayList<>();
        MaxFileCount = MaxFileCount == 0 ? 1 : MaxFileCount;
        if (uploadFiles.length == 0) {
            throw new Exc("未选择文件！");
        } else if (uploadFiles.length > MaxFileCount) {
            throw new Exc("上传文件数量不能大于" + MaxFileCount);
        }
        int skipCount = 0;
        for (MultipartFile file : uploadFiles) {
            ByteArrayOutputStream outputStream = null;
            InputStream inputStream = null;
            try{
            Map<String, Object> fileMap = new HashMap<String, Object>();
            if (file.getSize() > MaxFileSize) {
                skipCount += 1;
                log.error("已跳过保存文件:{}", getFileSizeStr(file.getSize()));
                continue;
            }
            // 获取上传文件的名字
            String fileName = file.getOriginalFilename();
            if (StringUtils.isBlank(fileName)) throw new SecurityException("未知文件名，无法上传！");
            fileMap.put("fileName", fileName);
            fileMap.put("fileSize", file.getSize());
            // 截取扩展名
            String fileExt = getFileUrlSuffix(fileName);
            // 校验该扩展名是否在该键对应的格式中
            if (typeEnum != FileTypeEnum.System && !typeEnum.isContains(fileExt)) {
                throw new Exc("上传文件扩展名是不允许的扩展名。\n只允许" + Arrays.toString(typeEnum.getSuffix()) + "格式。");
            }
            if (typeEnum == FileTypeEnum.Image && isCompression){
                /* 如果是图片就压缩该文件 */
                outputStream = new ByteArrayOutputStream();
                Thumbnails.of(file.getInputStream())
                        .scale(1f) //图片大小（长宽）压缩比例 从0-1，1表示原图
                        .outputQuality(0.5f) //图片质量压缩比例 从0-1，越接近1质量越好
                        .toOutputStream(outputStream);
                byte[] bytes = outputStream.toByteArray();
                inputStream = new ByteArrayInputStream(bytes);
            }else{
                inputStream = file.getInputStream();
            }
            fileMap.put("fileSuffix", fileExt);
            String ossFullName = AliyunOssUtil.buildOssFileName(typeEnum, String.format("%s.%s", IdUtils.create(), fileExt));
            PutObjectResult result = staticOss.putObject("just-game", ossFullName, inputStream);
            String ossFileName = String.format("https://%s.%s/%s", Const.OSS_BUCKET_NAME, Const.OSS_ENDPOINT, ossFullName);
            fileMap.put("filePath", ossFileName);
            paths.add(ossFileName);
            filesMap.add(fileMap);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if (outputStream != null){
                    outputStream.close();
                }
                if (inputStream != null){
                    inputStream.close();
                }
            }
        }
        if (skipCount > 0) throw new Exc("文件过大，上传失败！");
        return filesMap;
    }
}
